﻿//=============================================================================
// LazyParser.NET - C# Expression Parser for .NET 2.0 
//
// Copyright (c) 2008 Philippe Leybaert
//
// Permission is hereby granted, free of charge, to any person obtaining a copy 
// of this software and associated documentation files (the "Software"), to deal 
// in the Software without restriction, including without limitation the rights 
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is 
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in 
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//=============================================================================

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Activa.LazyParser
{
    public delegate Expression TokenEvaluator(string token, Expression[] terms);

    public abstract class ExpressionParser
    {
        private readonly List<TokenDefinition> _tokenDefinitions = new List<TokenDefinition>();
        private TokenEvaluator _functionEvaluator;
        private int _functionCallPrecedence;
        private IParserContext _defaultContext;

        private Regex _regEx = null;

        public TokenEvaluator FunctionEvaluator
        {
            get { return _functionEvaluator; }
            set { _functionEvaluator = value; }
        }

        public int FunctionCallPrecedence
        {
            get { return _functionCallPrecedence; }
            set { _functionCallPrecedence = value; }
        }

        public IParserContext DefaultContext
        {
            get { return _defaultContext; }
            set { _defaultContext = value; }
        }

        public void AddLeftParen(string pattern)
        {
            _tokenDefinitions.Add(new TokenDefinition(TokenType.LeftParen, _functionCallPrecedence, pattern));
        }

        public void AddRightParen(string pattern)
        {
            _tokenDefinitions.Add(new TokenDefinition(TokenType.RightParen, pattern));
        }

        public void AddArgumentSeparator(string pattern)
        {
            _tokenDefinitions.Add(new TokenDefinition(TokenType.ArgumentSeparator, pattern));
        }

        private void AddTokenDefinition(TokenDefinition tokenDefinition)
        {
            AddTokenDefinition(tokenDefinition, null);
        }

        private void AddTokenDefinition(TokenDefinition tokenDefinition, TokenEvaluator evaluator)
        {
            tokenDefinition.Evaluator = evaluator;

            TokenDefinition existing = _tokenDefinitions.Find(delegate(TokenDefinition td) { return td.Pattern == tokenDefinition.Pattern && !string.IsNullOrEmpty(td.Pattern); });

            if (existing != null)
                existing.Alternate = tokenDefinition;
            else
                _tokenDefinitions.Add(tokenDefinition);
        }

        public void AddUnaryOperator(string pattern, int precedence, TokenEvaluator evaluator)
        {
            AddTokenDefinition(new TokenDefinition(TokenType.UnaryOperator, precedence, OperatorAssociativity.Right, pattern), evaluator);
        }

        public void AddBinaryOperator(string pattern, int precedence, TokenEvaluator evaluator)
        {
            AddTokenDefinition(new TokenDefinition(TokenType.Operator, precedence, pattern), evaluator);
        }

        public void AddBinaryOperator(string pattern, int precedence, OperatorAssociativity associativity, TokenEvaluator evaluator)
        {
            AddTokenDefinition(new TokenDefinition(TokenType.Operator, precedence, associativity, pattern), evaluator);
        }

        public void AddTernaryOperator(string pattern1, string pattern2, int precedence, OperatorAssociativity associativity, TokenEvaluator evaluator)
        {
            TokenDefinition root = new TokenDefinition(TokenType.TernaryOperator, evaluator);

            TokenDefinition partial1 = new TokenDefinition(TokenType.TernaryOperator1, precedence, associativity, pattern1);
            TokenDefinition partial2 = new TokenDefinition(TokenType.TernaryOperator2, precedence, associativity, pattern2);

            partial1.Root = root;
            partial2.Root = root;

            AddTokenDefinition(partial1);
            AddTokenDefinition(partial2);
        }

        public void AddTerm(string pattern, TokenEvaluator evaluator)
        {
            AddTokenDefinition(new TokenDefinition(TokenType.Term, pattern), evaluator);
        }

        private string BuildRegex()
        {
            string s = "";

            for (int i = 0; i < _tokenDefinitions.Count; i++)
            {
                if (string.IsNullOrEmpty(_tokenDefinitions[i].Pattern))
                    continue;

                if (s.Length > 0)
                    s += "|";

                s += "(?<" + (i + 1) + ">" + _tokenDefinitions[i].Pattern + ")";
            }

            return s;
        }

        private RPNExpression ParseToRPN(string s)
        {
            if (_regEx == null)
            {
                _regEx = new Regex(BuildRegex(), RegexOptions.ExplicitCapture);
            }

            int numTokens = _tokenDefinitions.Count;

            RPNExpression rpnExpression = new RPNExpression(_functionEvaluator, _functionCallPrecedence);

            rpnExpression.Start();

            foreach (Match match in _regEx.Matches(s))
            {
                int group = -1;

                for (int i = 0; i < numTokens; i++)
                    if (match.Groups[i + 1].Success)
                    {
                        group = i;
                        break;
                    }

                rpnExpression.ApplyToken(new Token(_tokenDefinitions[group], match.Value));
            }

            rpnExpression.Finish();

            return rpnExpression;
        }

        private static readonly object _cacheLock = new object();
        private static readonly Dictionary<string, IExpression> _expressionCache = new Dictionary<string, IExpression>();

        public IExpression Parse(string s)
        {
            IExpression expression;

            lock (_cacheLock)
            {
                if (_expressionCache.TryGetValue(s, out expression))
                    return expression;

                expression = ParseToRPN(s).Compile();

                _expressionCache[s] = expression;
            }

            return expression;
        }

        public IExpressionWithContext ParseWithContext(string s, IParserContext context)
        {
            return new ExpressionWithContext(Parse(s), context);
        }

        public IExpressionWithContext ParseWithContext(string s)
        {
            return new ExpressionWithContext(Parse(s), _defaultContext);
        }

        public object EvaluateToObject(string s)
        {
            return ParseWithContext(s).EvaluateToObject();
        }

        public object Evaluate(string s, out Type type)
        {
            IValueWithType value = ParseWithContext(s).Evaluate();

            type = value.Type;

            return value.Value;
        }

        public IValueWithType Evaluate(string s)
        {
            return ParseWithContext(s).Evaluate();
        }

        public T Evaluate<T>(string s)
        {
            return ParseWithContext(s).Evaluate<T>();
        }

        public IValueWithType Evaluate(string s, IParserContext context)
        {
            return ParseWithContext(s, context).Evaluate();
        }

        public object EvaluateToObject(string s, IParserContext context)
        {
            return ParseWithContext(s, context).EvaluateToObject();
        }

        public object Evaluate(string s, out Type type, IParserContext context)
        {
            IValueWithType value = ParseWithContext(s, context).Evaluate();

            type = value.Type;

            return value.Value;
        }

        public T Evaluate<T>(string s, IParserContext context)
        {
            return ParseWithContext(s, context).Evaluate<T>();
        }
    }
}
